#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/utils.h>
#include <sobjc-commons/st.h>
#include "name.h"
#include "parse.h"

struct mth_table_entry {
  char *cls;
  char *inst;
};

struct sel_entry {
  unsigned long n;
  char *sel;
};

struct sel_iter {
  objc_sel_iter_f f;  
  unsigned long n;
  void *buf;
};

struct stret_entry {
  int n;
  char *func_name;
};

struct stret_iter {
  int n;
  char **type_names;
  char **func_names;
};

static char *make_imp_name(const char *, const char *, const char *);
static int stret_func_name_callback(char *, struct stret_entry *,
                                    struct stret_iter *);
static void convert_sel2id(char *);
static int sel_name_callback(char *, struct sel_entry *, struct sel_iter *);
static struct sel_entry *register_sel_name(const char *);

#define OBJC_PREFIX             "__objc_"
#define CLS_OBJ_PREFIX          OBJC_PREFIX "cls_"
#define CLS_MTH_FUNC_PREFIX     OBJC_PREFIX "cm_"
#define INST_MTH_FUNC_PREFIX    OBJC_PREFIX "im_"
#define GENERIC_MSG_PREFIX      OBJC_PREFIX "gs_"
#define SEND_MSG_STRET_PREFIX   OBJC_PREFIX "stret_"
#define SEL_PREFIX              OBJC_PREFIX "sel_"
#define INIT_PREFIX             OBJC_PREFIX "init_"

static st_table *mth_table = NULL;
static st_table *sel_table = NULL;
static st_table *stret_table = NULL;
static int inited = 0;
static unsigned long nsels = 0;
static int nstrets = 0;

unsigned long objc_used_sel_num = 0;

void
objc_init_name()
{
  if (!inited) {
    stret_table = st_init_strtable();
    sel_table = st_init_strtable();
    inited = 1;
  }
}

char *
objc_make_get_class_object_func_name(const char *name)
{
  char *func;
  size_t len;

  len = strlen(CLS_OBJ_PREFIX) + strlen(name);
  func = ALLOC_N(char, len+1);
  sprintf(func, CLS_OBJ_PREFIX "%s", name);

  return func;
}

char *
objc_make_imp_name(int rtype, const char *cls_name, const char *mth_name)
{
  return make_imp_name(cls_name, mth_name,
                       rtype ? INST_MTH_FUNC_PREFIX : 
                       CLS_MTH_FUNC_PREFIX);
}

static char *
make_imp_name(const char *cls_name,
              const char *mth_name,
              const char *prefix)
{
  size_t size;
  char *name;

  size = strlen(prefix) + strlen(cls_name) + strlen(mth_name) + 2;
  name = ALLOC_N(char, size);
  sprintf(name, "%s%s_%s", prefix, cls_name, mth_name);
  convert_sel2id(name);

  return name;
}

char *
objc_register_send_msg_stret_func_name(char *type_name)
{
  struct stret_entry *entry = NULL;

  st_lookup(stret_table, type_name, (st_data_t *)&entry);
  if (entry == NULL) {
    entry = ALLOC(struct stret_entry);
    entry->n = nstrets++;
    entry->func_name = ALLOC_N(char, strlen(SEND_MSG_STRET_PREFIX) + 9);
    sprintf(entry->func_name, SEND_MSG_STRET_PREFIX "%08d", entry->n);
    st_insert(stret_table, type_name, entry);
  }

  return entry->func_name;
}

void
objc_iter_send_msg_stret_func_names(void (*f)(char *, char *))
{
  struct stret_iter it;
  int i;

  it.n = 0;
  it.type_names = ALLOC_N(char *, nstrets);
  it.func_names = ALLOC_N(char *, nstrets);
  st_foreach(stret_table, stret_func_name_callback, &it);

  for (i = 0; i < nstrets; i++)
    f(it.type_names[i], it.func_names[i]);

  objc_free(it.type_names);
  objc_free(it.func_names);
}

static int
stret_func_name_callback(char *type_name, struct stret_entry *e,
                         struct stret_iter *it)
{
  it->n++;
  it->type_names[e->n] = type_name;
  it->func_names[e->n] = e->func_name;

  if (it->n <= nstrets)
    return ST_CONTINUE;
  else
    return ST_STOP;
}

static void
convert_sel2id(char *name)
{
  size_t i;

  for (i = 0; i < strlen(name); i++)
    if (name[i] == ':')
      name[i] = '_';
}

char *
objc_make_init_func_name(const char *file)
{
  char *name;
  size_t i;

  name = ALLOC_N(char, strlen(INIT_PREFIX) + strlen(file) + 1);
  sprintf(name, INIT_PREFIX "%s", file);
  for (i = 0; i < strlen(name); i++) {
    if (name[i] == '.') {
      name[i] = 0;
      break;
    } else if (!isalnum(name[i])) {
      name[i] = '_';
    }
  }

  return name;
}

void
objc_register_sel_name(const char *name)
{
  register_sel_name(name);
}

static struct sel_entry * 
register_sel_name(const char *name)
{
  struct sel_entry *entry = NULL;

  if (!st_lookup(sel_table, name, (st_data_t *)&entry)) { 
    nsels++;
    entry = ALLOC(struct sel_entry);
    entry->n = -1;
    entry->sel = objc_strdup(name);
    st_insert(sel_table, entry->sel, (st_data_t)entry);
  }
  return entry;
}

void
objc_assign_sel_num(const char *name)
{
  struct sel_entry *entry = NULL;

  entry = register_sel_name(name);
  if (entry->n == OBJC_NOT_USED_SEL)
    entry->n = objc_used_sel_num++;
}

unsigned long
objc_get_sel_num(const char *name)
{
  struct sel_entry *entry = NULL;

  st_lookup(sel_table, name, (st_data_t *)&entry);
  if (entry != NULL)
    return entry->n;
  else
    return OBJC_NOT_USED_SEL;
}

void
objc_iter_sels(objc_sel_iter_f f, void *buf)
{
  struct sel_iter it;

  it.f = f;
  it.n = 0;
  it.buf = buf;
  st_foreach(sel_table, sel_name_callback, &it);
}

static int
sel_name_callback(char *name, struct sel_entry *e, struct sel_iter *it)
{
  it->f(e->n, e->sel, it->buf);
  it->n++;

  if (it->n <= nsels)
    return ST_CONTINUE;
  else
    return ST_STOP;
}

